package cn.iocoder.yudao.framework.server;

import cn.iocoder.yudao.framework.common.GameServerUtil;
import cn.iocoder.yudao.framework.common.NettyFrameworkConfig;
import cn.iocoder.yudao.framework.utils.ExecutorUtils;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.buffer.PooledByteBufAllocator;
import io.netty.channel.*;
import io.netty.channel.epoll.Epoll;
import io.netty.channel.epoll.EpollServerSocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.ApplicationContext;

import java.util.concurrent.TimeUnit;


public class NettyWsNetworkServer {

    private Logger logger = LoggerFactory.getLogger(NettyWsNetworkServer.class);
    private ApplicationContext context;
    private EventLoopGroup bossGroup = null;
    private EventLoopGroup workerGroup = null;
    private ChannelFuture channelFuture;

    public NettyWsNetworkServer(ApplicationContext context) {
        this.context = context;
    }


    /**
     * 启动Netty服务器
     * # Linux查看缓冲区设置
     * ss -ntmp
     * <p>
     * # 查看网络统计
     * cat /proc/net/netstat | grep -E 'TcpExt|IpExt'
     * <p>
     * 关键指标：
     * <p>
     * TCPBacklogDrop：说明SO_BACKLOG不足
     * <p>
     * TCPRcvBufErrors：说明SO_RCVBUF太小
     * <p>
     * TCPSndBufErrors：说明SO_SNDBUF不足
     * <p>
     * 这些配置可以完美协同工作，共同实现：
     * <p>
     * 操作系统级的物理缓冲区控制（SO_*BUF）
     * <p>
     * 应用层的智能内存管理（Allocator）
     * <p>
     * 背压控制机制（WaterMark）
     *
     * @param channelInitializer 通道初始化器，用于配置ChannelPipeline
     */
    public void start(ChannelInitializer<Channel> channelInitializer) {
        // ==================== 1. 获取基础配置 ====================
        // 从Spring容器获取Netty配置Bean
        final NettyFrameworkConfig serverConfig = context.getBean(NettyFrameworkConfig.class);

        // ==================== 2. 初始化线程组 ====================
        // Boss线程组 - 处理客户端连接请求（accept），连接建立完后，交由 worker 线程组处理 I/O
        final int bossThreads = serverConfig.getBossThreads();
        bossGroup = ExecutorUtils.newNioEventLoopGroup(bossThreads, "netty-boss");

        // Worker线程组 - 处理已连接客户端的读写（I/O），每个连接由一个线程负责读写事件
        final int workerThreads = serverConfig.getWorkThreads();
        workerGroup = ExecutorUtils.newNioEventLoopGroup(workerThreads, "netty-worker");

        // ==================== 3. 配置服务器引导 ====================
        final ServerBootstrap bootstrap = new ServerBootstrap();
        try {
            // 3.1 基础配置
            bootstrap.group(bossGroup, workerGroup)
                    // 根据OS选择Channel类型
                    .channel(GameServerUtil.isLinuxPlatform() && Epoll.isAvailable() ? EpollServerSocketChannel.class : NioServerSocketChannel.class)

                    // 3.2 父Channel选项（ServerSocketChannel配置）
                    .option(ChannelOption.SO_BACKLOG, serverConfig.getBacklog())  // 等待连接队列大小
                    .option(ChannelOption.SO_REUSEADDR, true) // 端口复用
                    .option(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT) // 使用内存池
                    .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, serverConfig.getConfirmTimeout())

                    // 3.3 子Channel选项（SocketChannel配置）
                    .childOption(ChannelOption.TCP_NODELAY, true) // 禁用Nagle算法
                    .childOption(ChannelOption.SO_KEEPALIVE, true) // 启用TCP心跳
                    .childOption(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT)
                    .childOption(ChannelOption.SO_SNDBUF, serverConfig.getSendBuffSize())
                    .childOption(ChannelOption.SO_RCVBUF, serverConfig.getReceiveBuffSize())
                    .childOption(ChannelOption.RCVBUF_ALLOCATOR,
                            new AdaptiveRecvByteBufAllocator(
                                    64,      // 初始预估大小(64字节)
                                    1024,    // 最小预分配空间(1KB)
                                    65536))  // 最大单次分配空间(64KB)
                    .childOption(ChannelOption.WRITE_BUFFER_WATER_MARK,
                            new WriteBufferWaterMark(
                                    32 * 1024,  // 低水位线(32KB)
                                    64 * 1024))  // 高水位线(64KB)

                    // 3.4 设置Channel处理器
                    .childHandler(channelInitializer);

            // ==================== 4. 启动服务器 ====================
            logger.info("正在启动Netty服务，端口: {}", serverConfig.getNettyPort());

            // 绑定端口并同步等待启动完成
            channelFuture = bootstrap.bind(serverConfig.getNettyPort()).sync();
            channelFuture.addListener(f -> {
                if (!f.isSuccess()) {
                    // 连接达到上限时的处理
                    logger.warn("连接数已达上限，拒绝新连接");
                }
            });

            // 记录成功日志
            logger.info("Netty服务启动成功，端口: {}", serverConfig.getNettyPort());

            // 阻塞直到服务器通道关闭（长连接场景）
            channelFuture.channel().closeFuture().sync();

        } catch (InterruptedException e) {
            // 线程中断处理
            Thread.currentThread().interrupt();
            logger.error("服务器启动被中断", e);
        } catch (Exception e) {
            // 异常处理
            logger.error("Netty服务启动失败，端口: {}", serverConfig.getNettyPort(), e);

            // 建议通过监听器通知上层应用，而非直接退出
            throw new IllegalStateException("Netty服务启动失败", e);
        } finally {
            // 5. 添加JVM关闭钩子确保资源释放
            Runtime.getRuntime().addShutdownHook(new Thread(this::shutdown));
        }
    }

    /**
     * 资源关闭方法
     */
    private void shutdown() {
        logger.info("正在关闭Netty服务...");

        // 先关闭Channel
        if (channelFuture != null) {
            channelFuture.channel().close();
        }

        // 优雅关闭线程组
        if (bossGroup != null) {
            bossGroup.shutdownGracefully(0, 5, TimeUnit.SECONDS)
                    .addListener(f -> logger.debug("Boss线程组已关闭"));
        }

        if (workerGroup != null) {
            workerGroup.shutdownGracefully(0, 5, TimeUnit.SECONDS)
                    .addListener(f -> logger.debug("Worker线程组已关闭"));
        }

        logger.info("Netty服务已完全关闭");
    }

    public void stop() {

        if (channelFuture != null) {
            channelFuture.channel().close();
        }
        int quietPeriod = 5;
        int timeout = 30;
        TimeUnit timeUnit = TimeUnit.SECONDS;
        if (workerGroup != null) {
            workerGroup.shutdownGracefully(quietPeriod, timeout, timeUnit);
        }
        if (bossGroup != null) {
            bossGroup.shutdownGracefully(quietPeriod, timeout, timeUnit);
        }
    }
}